package com.sxhuayuan.parking.utils;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.validation.constraints.NotNull;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.fastjson.annotation.JSONField;
import com.sxhuayuan.parking.exception.SystemException;
import com.sxhuayuan.parking.plugin.annotation.SignatureField;

public class BeanUtils {

	private static Logger log = LoggerFactory.getLogger(BeanUtils.class);

	/**
	 * 检查bean里标记为@Required的field是否为空，为空则抛异常
	 *
	 * @param bean
	 *            要检查的bean对象
	 * @throws WxErrorException
	 */
	public static void checkRequiredFields(Object bean) throws SystemException {
		List<String> requiredFields = new ArrayList<>();

		List<Field> fields = new ArrayList<>(Arrays.asList(bean.getClass().getDeclaredFields()));
		fields.addAll(Arrays.asList(bean.getClass().getSuperclass().getDeclaredFields()));
		for (Field field : fields) {
			try {
				boolean isAccessible = field.isAccessible();
				field.setAccessible(true);
				if (field.isAnnotationPresent(NotNull.class)) {
					if (field.get(bean) == null || (field.get(bean) instanceof String && StringUtils.isBlank(field.get(bean).toString()))) {
						// 两种情况，一种是值为null，另外一种情况是类型为字符串，但是字符串内容为空的，都认为是没有提供值
						requiredFields.add(field.getName());
					}
				}
				field.setAccessible(isAccessible);
			} catch (SecurityException | IllegalArgumentException | IllegalAccessException e) {
				e.printStackTrace();
			}
		}

		if (!requiredFields.isEmpty()) {
			String msg = "必填字段 " + requiredFields + " 必须提供值";
			log.debug(msg);
			throw new SystemException(msg);
		}
	}

	/**
	 * 将bean按照@JSONField标识的字符串内容生成以之为key的map对象
	 *
	 * @param bean
	 *            包含@JSONField的xml bean对象
	 * @return map对象
	 */
	public static Map<String, String> jsonBean2Map(Object bean) {
		Map<String, String> result = new LinkedHashMap<>();
		List<Field> fields = new ArrayList<>(Arrays.asList(bean.getClass().getDeclaredFields()));
		fields.addAll(Arrays.asList(bean.getClass().getSuperclass().getDeclaredFields()));
		for (Field field : fields) {
			try {
				boolean isAccessible = field.isAccessible();
				field.setAccessible(true);
				if (field.get(bean) == null) {
					field.setAccessible(isAccessible);
					continue;
				}
				if (!field.isAnnotationPresent(JSONField.class)) {
					result.put(field.getName(), field.get(bean).toString());
				} else if (field.getAnnotation(JSONField.class).serialize()) {
					result.put(field.getAnnotation(JSONField.class).name(), field.get(bean).toString());
				}

				field.setAccessible(isAccessible);
			} catch (SecurityException | IllegalArgumentException | IllegalAccessException e) {
				e.printStackTrace();
			}

		}

		return result;
	}

	/**
	 * 签名需要的字段
	 * 
	 * @param bean
	 * @return
	 */
	public static Map<String, String> jsonBean2SignMap(Object bean, boolean ignoreNullValue) {
		Map<String, String> result = new LinkedHashMap<>();
		List<Field> fields = new ArrayList<>(Arrays.asList(bean.getClass().getSuperclass().getDeclaredFields()));
		fields.addAll(Arrays.asList(bean.getClass().getDeclaredFields()));
		for (Field field : fields) {
			try {
				if (Modifier.isStatic(field.getModifiers()))
					continue;
				boolean isAccessible = field.isAccessible();
				field.setAccessible(true);
				Object value = field.get(bean);
				if (value == null && ignoreNullValue) {
					field.setAccessible(isAccessible);
					continue;
				}
				SignatureField signatureField = field.getAnnotation(SignatureField.class);

				if (signatureField == null) {
					result.put(field.getName(), value == null ? "" : String.valueOf(value));
				} else if (signatureField.serialize()) {
					String key = StringUtils.isEmpty(signatureField.name()) ? field.getName() : signatureField.name();
					result.put(key, String.valueOf(value));
				}

				field.setAccessible(isAccessible);
			} catch (SecurityException | IllegalArgumentException | IllegalAccessException e) {
				e.printStackTrace();
			}

		}

		return result;
	}

	/**
	 * 获得属性
	 * 
	 * @param bean
	 * @param propertyName
	 * @return
	 */
	public static <T> T getBeanProperty(Object bean, String propertyName, Class<T> cls) {
		validateArgs(bean, propertyName, cls);
		// try getters first
		Method getter = getMethod(bean, propertyName);
		if (getter != null) {
			try {
				return cls.cast(getter.invoke(bean));
			} catch (Exception e) {
				/**/
			}
		}

		// then try fields
		Field field = getField(bean, propertyName);
		if (field != null) {
			try {
				field.setAccessible(true);
				return cls.cast((Object) field.get(bean));
			} catch (Exception e) {
				/**/
			}
		}

		return null;
	}

	private static void validateArgs(Object bean, String propertyName, Class<?> cls) {
		if (bean == null) {
			throw new IllegalArgumentException("bean is null");
		}
		if (propertyName == null) {
			throw new IllegalArgumentException("propertyName is null");
		}
		if (propertyName.trim().length() == 0) {
			throw new IllegalArgumentException("propertyName is empty");
		}
		if (cls == null) {
			throw new IllegalArgumentException("class is null");
		}
	}

	private static Method getMethod(Object bean, String propertyName) {
		StringBuilder sb = new StringBuilder("get").append(Character.toUpperCase(propertyName.charAt(0)));
		if (propertyName.length() > 1) {
			sb.append(propertyName.substring(1));
		}
		String getterName = sb.toString();
		for (Method m : bean.getClass().getMethods()) {
			if (getterName.equals(m.getName()) && m.getParameterTypes().length == 0) {
				return m;
			}
		}
		return null;
	}

	public static Field getField(Object bean, String propertyName) {
		for (Field f : bean.getClass().getDeclaredFields()) {
			if (propertyName.equals(f.getName())) {
				return f;
			}
		}
		return null;
	}

	public static boolean setField(Object bean, String name, Object value) {
		Field field;
		try {
			field = bean.getClass().getDeclaredField(name);
			if (field != null) {
				boolean access = field.isAccessible();
				field.setAccessible(true);
				field.set(bean, value);
				field.setAccessible(access);
				return true;
			}
		} catch (NoSuchFieldException e1) {
			e1.printStackTrace();
		} catch (SecurityException e1) {
			e1.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}

		return false;
	}
}
